import logging
import os
import unittest

# from chernc.analyzer.c_rust_matcher import match, match_with_explaination
from chernc.analyzer.matcher import CherncMatcher, explain_match
from chernc.constants import DEFAULT_LOGGER_DIR, DEFAULT_LOGGER_FILE_NAME
from chernc.logging import build_logger
from chernc.parser.cpp_parser import parse_cpp
from chernc.utils.md_utils import extract_code_blocks_with_language

logger: logging.Logger = build_logger(
    "test_c_rust_matcher", logger_filename=DEFAULT_LOGGER_FILE_NAME, logger_dir=DEFAULT_LOGGER_DIR
)


class FakeSlice:
    def __init__(self, t):
        self.text = t
        self.loc = 0
        self.decls = []


class FakeChunk:
    def __init__(self, slices):
        self.slices = slices


# return a lambda, bu do not add a
def generate_method_from_md_file(md_string, case=True):
    code_blocks = extract_code_blocks_with_language(md_string)
    cpp_block = ""
    rust_block = ""
    for l, c in code_blocks:
        if l == "cpp":
            cpp_block = c
        elif l == "rust":
            rust_block = c
    if cpp_block == "":
        raise Exception(f"the md file does not have a cpp code block!\n:{md_string}")
    if rust_block == "":
        raise Exception(f"the md file does not have a rust code block!\n:{md_string}")

    cpp_nodes = parse_cpp(cpp_block).root_node.children
    # rust_nodes = parse_rust(rust_block).root_node.children
    # rust_nodes = [i for i in rust_nodes]

    chunk = FakeChunk([FakeSlice(n.text.decode("utf-8")) for n in cpp_nodes])

    # s = FakeSlice(cpp_block)
    def closure(self):
        # get the cpp block
        # matcher = CherncMatcher(chunk)
        matcher = CherncMatcher.createforChunk(chunk)
        good_message = None

        def raiseError(msg):
            nonlocal good_message
            good_message = msg

        if case:
            # ok, reason = match_with_explaination(extractPiecesFromSlice(s), rust_nodes)

            # ok, reason = matcher.match_with_explanation()
            ok = matcher._match(rust_block, raiseError)
            self.assertIsNone(good_message)

            logger.debug(explain_match(ok))
            # self.assertIsNotNone(ok)
            # logger.debug(ok.result_table)

        else:
            # ok, reason = match_with_explaination(extractPiecesFromSlice(s), rust_nodes)
            ok = matcher._match(rust_block, raiseError)
            self.assertIsNotNone(good_message)
            logger.debug(explain_match(ok))

    return closure


# return a lambda, bu do not add a
def generate_method_from_md_file_amap(md_string, case=True):
    code_blocks = extract_code_blocks_with_language(md_string)
    cpp_block = ""
    rust_block = ""
    for l, c in code_blocks:
        if l == "cpp":
            cpp_block = c
        elif l == "rust":
            rust_block = c
    if cpp_block == "":
        raise Exception(f"the md file does not have a cpp code block!\n:{md_string}")
    if rust_block == "":
        raise Exception(f"the md file does not have a rust code block!\n:{md_string}")

    cpp_nodes = parse_cpp(cpp_block).root_node.children
    # rust_nodes = parse_rust(rust_block).root_node.children
    # rust_nodes = [i for i in rust_nodes]

    chunk = FakeChunk([FakeSlice(n.text.decode("utf-8")) for n in cpp_nodes])

    # s = FakeSlice(cpp_block)
    def closure(self):
        # get the cpp block
        # matcher = CherncMatcher(chunk)
        matcher = CherncMatcher.createforChunk(chunk)

        if case:
            # ok, reason = match_with_explaination(extractPiecesFromSlice(s), rust_nodes)

            matchresult = matcher.try_to_match(rust_block)

            logger.debug(explain_match(matchresult))
            # self.assertIsNotNone(ok)
            # logger.debug(ok.result_table)
            # for k, v in ok.result_table.items():
            # logger.debug(k.text)
            #    if v is not None:
            #        logger.debug("===source===")
            #        logger.debug(k.text)
            #        logger.debug("===matced===")
            #        logger.debug(v.matched_text)
            #        logger.debug("===endsource===")
            #    else: logger.debug(f"None matched slice! the slice{k} with:\n {k.text} has no matched things!")

        else:
            # ok, reason = match_with_explaination(extractPiecesFromSlice(s), rust_nodes)
            matchresult = matcher.try_to_match(rust_block)

            logger.debug(explain_match(matchresult))

    return closure


class TestComparatorRules(unittest.TestCase):
    pass


class TestAsMuchAsPossible(unittest.TestCase):
    pass


# return a lambda, bu do not add a


def dynamically_add_methods_from_md_directory(k, directory, generator, case=True):
    # first, read all md files from the directory
    md_files = [f for f in os.listdir(directory) if f.endswith(".md")]
    # then, generate a method from the file
    for md in md_files:
        with open(os.path.abspath(os.path.join(directory, md)), "r", encoding="utf-8") as f:
            content = f.read()
            method = generator(content, case=case)
            setattr(k, "_".join(["test", md]), method)


dynamically_add_methods_from_md_directory(
    TestComparatorRules, os.path.abspath(os.path.join(os.path.dirname(__file__), "c_rust_cases")), generate_method_from_md_file
)
dynamically_add_methods_from_md_directory(
    TestComparatorRules,
    os.path.abspath(os.path.join(os.path.dirname(__file__), "c_rust_cases_false")),
    generate_method_from_md_file,
    False,
)
dynamically_add_methods_from_md_directory(
    TestAsMuchAsPossible,
    os.path.abspath(os.path.join(os.path.dirname(__file__), "c_rust_cases_amap")),
    generate_method_from_md_file_amap,
)

if __name__ == "__main__":
    unittest.main()
